#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>


int main(int argc, char** argv)
{
    //socket 函数：创建一个新套接字
    // 参数意义：
    // 第一个参数：地址家族，通常是 AF_INET
    // 第二个参数：套接字类型，通常有两种：SOCK_STREAM（流套接字，用于 TCP 协议通信） 和 SOCK_DGRAM（数据报式套接字，用于 UDP 协议通信）
    // 第三个参数：通常为 0， 表示使用默认协议。
    // 返回值为新套接字的文件描述符，如果失败则为 -1

    // 第一步：创建一个新的监听套接字
    int sock_listen = socket(AF_INET, SOCK_STREAM, 0);

    if(-1 == sock_listen)
    {
        perror("socket");
        return 1;
    }

    // 第二步：绑定地址

    // 指定地址信息
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;            // 指定地址家族为 Internet 地址家族
    myaddr.sin_addr.s_addr = INADDR_ANY;    // 指定 IP 地址为本机任意地址
    //myaddr.sin_addr.s_addr = inet_addr("192.168.0.56");    // 指定 IP 地址为本机某个确定的 IP 地址
    myaddr.sin_port = htons(8888);          // 指定端口号为 8888
    // htons：将一个短整型数据从主机字节序（通常是小端）转换为网络字节序（通常是大端）
    // inet_addr：将字符串形式的 IP 地址转换为无符号 32 位整数，并且是网络字节序

    // 将上面指定的地址和套接字绑定
    if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
    {
        perror("bind");
        return 1;
    }

    // 第三步：监听
    // listen 函数的第二个参数为监听等待队列的长度
    if(-1 == listen(sock_listen, 5))
    {
        perror("listen");
        return 1;
    }

    while(1)
    {
        // 第四步：接受客户端连接请求

        //accept(sock_listen, NULL, NULL);  // 如果对客户端地址信息不感兴趣

        // 如果想获取当前客户端的地址信息，就使用下面的方式
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);

        // 调用 accept 函数接收一个客户端连接请求（队头请求）
        // 如果成功，返回值为一个套接字描述符，这个套接字是和该客户端一一对应的
        // 这个套接字专门用于和对应的客户端通信，所以通常称它为连接套接字。
        // 如果当前没有任何客户端连接请求到来，accept 函数将会阻塞当前线程，直到成功接收到一个连接请求或出错
        int sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &len);

        if(-1 == sock_conn)
        {
            perror("accept");
            continue;
        }

        pid_t pid = fork();

        if(-1 == pid)
        {
            perror("fork");
            close(sock_conn);
            continue;
        }

        if(pid > 0)
        {
            // 父进程只负责接收客户端连接请求，并创建子进程
            close(sock_conn);
            continue;
        }

        // 子进程负责与对应的客户端通信
        close(sock_listen);

        printf("\n%s:%hu 已连接...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        // 第五步：收发数据

        // 发送数据
        char file_name[300];
        char msg[1024];
        int ret;
        unsigned long cnt1 = 0, cnt2 = 0;
        time_t start, end;
        char* p = NULL;

        p = strrchr(argv[1], '/');

        if(NULL == p)
        {
            strcpy(file_name, argv[1]);
        }
        else
        {
            strcpy(file_name, p + 1);
        }

        send(sock_conn, file_name, sizeof(file_name), 0);

        int fd = open(argv[1], O_RDONLY);

        printf("正在努力发送文件...\n");

        time(&start);

        while((ret = read(fd, msg, sizeof(msg))) > 0)
        {
            cnt1 += ret;

            ret = send(sock_conn, msg, ret, 0);
            //write(sock_conn, msg, sizeof(msg));  // 和上面的写法等效

            if(ret == -1) 
            {
                printf("发送文件失败！\n");
                break;
            }

            cnt2 += ret;
        }

        if(cnt1 != 0 && cnt1 == cnt2)
        {
            time(&end);
            //printf("发送文件成功！（耗时 %lu 秒，平均网速 %lu B/s）\n", end - start, cnt1 / (end -start));
            printf("发送文件成功！（耗时 %lu 秒）\n", end - start);
        }

        // 第六步：断开客户端连接
        close(sock_conn);

        exit(0);
    }

    // 第七步：关闭监听套接字
    close(sock_listen);
    
    return 0;
}
